Skip to main content

Chapter 13 - Foreach

You can create resources from a list of strings (called a map)

You cannot use count with foreach.

Intro to each.key and each.value

Demo


THe part that confused me the most about this was that the for_each block ended, and then the each.key or each.value was independent. The "myrg" part in the resource identifier also confused me.

I would think something like

foreach (value in values)
{
resource azurerm_rg "myrg-value"
{
name = value
value = value
}

}

Terraform's foreach does it this way:

# Build Azure Resource Group
resource "azurerm_resource_group" "myrg" {
for_each = {
dc1apps = "eastus"
dc2apps = "eastus2"
dc3apps = "westus"
}
name = "${each.key}-rg"
location = each.value
}

How this looks during a plan and apply is this: image.png

Toset


What is a set in terraform?

  • must be of the same type - strings, numbers, but not both.
  • cannot contain duplicate values

Terraform console


Paste these into the terraform console:

# Terraform console
terraform console

# All Strings to Strings
toset(["solomon", "jordan", "liz"])

# Mixed Type (Strings and Numbers) - Converted to Strings
toset(["solo", "liz", 123, 456])

# Removes duplicates (Set collections are unordered and cannot contain duplicate values,)
toset(["z", "k", "r", "a", "k"])

# Also arranges in the order (The order provided will be gone) - In short set collections are unordered
toset([4, 100, 20, 11, 21, 7, 6, 4, 100])

It places these in alphabetical order:

image.png

It changed the numbers to strings: image.png

Or numerical order: image.png

As terraform code:


resource "azurerm_resource_group" "myrg" {
for_each = toset([ "eastus", "eastus2", "westus" ])
name = "myrg-${each.value}"
location = each.key
}
/*
each.value is the same as each.key in this case
*/

Something like this would work:

resource "azurerm_resource_group" "myrg" {
for_each = toset([ "dev", "qa", "uat", "perf", "prod" ])
name = "appaa123-${each.value}"
location = "centralus"
}

Foreach Chaining


You created one set of RGs with a foreach, what happens when you want other resources to follow this same foreach? Foreach Chaining :)

Reference: https://developer.hashicorp.com/terraform/language/meta-arguments/for_each#chaining-for_each-between-resources

resource "azurerm_resource_group" "myrg" {
for_each = toset([ "dev", "dev2", "dev3", "qa", "uat", "stage", "prod" ])
name = "appaa123-${each.value}"
location = "centralus"
}

# Create Virtual Network
resource "azurerm_virtual_network" "myvnet" {
for_each = azurerm_resource_group.myrg
name = "appaa123-vnet-${each.key}"
address_space = ["10.0.0.0/16"]
location = azurerm_resource_group.myrg[each.key].location
resource_group_name = azurerm_resource_group.myrg[each.key].name
}

# Create Subnet
resource "azurerm_subnet" "mysubnet" {
for_each = azurerm_virtual_network.myvnet
name = "appaa123-sn-${each.key}"
resource_group_name = azurerm_resource_group.myrg[each.key].name
virtual_network_name = azurerm_virtual_network.myvnet[each.key].name
address_prefixes = ["10.0.2.0/24"]
}

# Create Azure Public IP Address
resource "azurerm_public_ip" "mypublicip" {
for_each = azurerm_subnet.mysubnet
name = "appaa123-puip-${each.key}"
resource_group_name = azurerm_resource_group.myrg[each.key].name
location = azurerm_resource_group.myrg[each.key].location
allocation_method = "Static"
domain_name_label = "app1-${each.key}-${random_string.myrandom.id}"
}

# # Create Network Interface
resource "azurerm_network_interface" "myvmnic" {
for_each = azurerm_public_ip.mypublicip
name = "appaa123-nic-${each.key}"
location = azurerm_resource_group.myrg[each.key].location
resource_group_name = azurerm_resource_group.myrg[each.key].name

ip_configuration {
name = "internal"
subnet_id = azurerm_subnet.mysubnet[each.key].id
private_ip_address_allocation = "Dynamic"
public_ip_address_id = azurerm_public_ip.mypublicip[each.key].id
}
}


# # Resource: Azure Linux Virtual Machine
resource "azurerm_linux_virtual_machine" "mylinuxvm" {
#for_each = toset(["vm1", "vm2"])
for_each = azurerm_network_interface.myvmnic #for_each chaining
name = "appaa123-vm-${each.key}"
computer_name = "appaa123-vm-${each.key}" # Hostname of the VM
resource_group_name = azurerm_resource_group.myrg[each.key].name
location = azurerm_resource_group.myrg[each.key].location
size = "Standard_DS1_v2"
admin_username = "azureuser"
network_interface_ids = [azurerm_network_interface.myvmnic[each.key].id]
admin_ssh_key {
username = "azureuser"
public_key = file("${path.module}/ssh-keys/terraform-azure.pub")
}
os_disk {
name = "osdisk${each.key}"
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
#disk_size_gb = 20
}
source_image_reference {
publisher = "RedHat"
offer = "RHEL"
sku = "83-gen2"
version = "latest"
}
custom_data = filebase64("${path.module}/app-scripts/app1-cloud-init.txt")
}